#pragma once

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include "Log.hpp"
#include "Util.hpp"

#define SEP ": "
#define WEB_ROOT "output/wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"

#define OK 200
#define NOT_FOUND 404

#define BAD_REQUEST 400
#define SERVER_ERROR 500

static std::string Code2Desc(int code)// 将状态码转换为字符串
{
    std::string desc;
    switch(code){
        case 200:
            desc = "OK";
            break;
        case 404:
            desc = "Not Found";
            break;
        default:
            break;
    }
    return desc;
}

// 转换后缀
static std::string Suffix2Desc(const std::string &suffix)
{
    static std::unordered_map<std::string,std::string> suffix2desc = {
        {".html","text/html"},
        {".css","text/css"},
        {".js","application/javascript"},
        {".jpg","application/x-jpg"},
        {".xml","application/xml"}
    };

    auto iter = suffix2desc.find(suffix);
    if(iter != suffix2desc.end()){
        return iter -> second;
    }
    return "text/html";
}

class HttpRequest{
    public:
        std::string request_line;// 请求行
        std::vector<std::string> request_header;// 请求报头
        std::string blank;// 请求空行
        std::string request_body;// 请求正文

        // 请求行的解析
        std::string method;
        std::string uri;
        std::string version;
			
		// 请求报头解析
        std::unordered_map<std::string, std::string> header_kv;// 属性名：属性信息
        int content_length;// 请求正文长度

		// URI解析
        std::string suffix;// 请求文件名的后缀
        std::string path;// URI路径
        std::string query_string;// URI参数

        bool cgi;// cgi处理标志符
        int size;// 打开的文件大小
    public:
        HttpRequest():content_length(0),cgi(false){}
        ~HttpRequest(){}
};

class HttpResponse{
    public:
        std::string status_line;// 响应行
        std::vector<std::string> response_header;// 响应报头
        std::string blank;// 响应空行
        std::string response_body;// 响应正文  

        int status_code;// 响应状态码
        int fd;// 暂时保存文件描述符
    public:   
        HttpResponse():blank(LINE_END),status_code(OK),fd(-1){}
        ~HttpResponse(){}
};

// 读取请求，分析请求，构建响应
class EndPoint{
    private:
        int sock;
        HttpRequest http_request;
        HttpResponse http_response;
        bool stop;
    private:
        // 读取请求行
        bool RecvHttpRequestLine()
        {
            auto& line = http_request.request_line;
            if(Util::ReadLine(sock,line) > 0){
                line.resize(line.size()-1);// 去除行尾 \n
                LOG(INFO,http_request.request_line);// 记录日志
            }
            else{
                stop = true;// 标记读取失败
            }
            return stop;
        }

        // 读取请求报头
        bool RecvHttpRequestHeader()
        {
            std::string line;
            while(line!="\n"){
                line.clear();// 读取前清空line
                if(Util::ReadLine(sock,line) <= 0){// 按行读取
                    stop = true;
                    break;
                }
                if(line == "\n"){// 读取到空行，请求报头已读完
                    http_request.blank = line;
                    break;
                }
                line.resize(line.size()-1);// 去除最后的'\n'
                http_request.request_header.push_back(line);// 将读取到的报头属性尾插入请求报头中
                LOG(INFO,line);
            }
            return stop;
        }

        // 解析请求行
        void ParseHttpRequestLine()
        {
            auto &line = http_request.request_line;// 获取请求行
            std::stringstream ss(line);
            ss >> http_request.method >> http_request.uri >> http_request.version;
            auto &method = http_request.method;// 获取请求行中的请求方法
            std::transform(method.begin(),method.end(),method.begin(),::toupper);// 字母转大写，方便后文使用
        }

        // 解析报头
        void ParseHttpRequestHeader()
        {
            std::string key;
            std::string value;
            for(auto &iter : http_request.request_header)
            {
                if(Util::CutString(iter,key,value,SEP)){
                    http_request.header_kv.insert({key,value});
                }
            }
        }

        // 判断是否需要读取请求正文
        bool IsNeedRecvHttpRequestBody()
        {
            auto &method = http_request.method;
            if(method == "POST"){// 请求方法为POST时可能存在请求正文
                auto &header_kv = http_request.header_kv;
                auto iter = header_kv.find("Content-Length");
                if(iter != header_kv.end()){
                    LOG(INFO,"Post Method, Content-Length: "+iter->second);
                    http_request.content_length = atoi(iter->second.c_str());// 将字符串转换为数字
                    return true;
                }
            }
            return false;
        }

        // 读取请求正文
        bool RecvHttpRequestBody()
        {
            if(IsNeedRecvHttpRequestBody()){// 判断是否需要读取请求正文
                int content_length = http_request.content_length;
                auto &body = http_request.request_body;

                char ch = 0;
                while(content_length){
                    ssize_t s = recv(sock,&ch,1,0);// 从套接字中读取请求正文
                    if(s > 0){
                        body.push_back(ch);
                        content_length--;
                    }
                    else{
                        stop = true;
                        break;
                    }
                }
                LOG(INFO, body);
            }
            return stop;
        }

        // CGI处理
        int ProcessCgi()
        {
            int code = OK;
            LOG(INFO,"process cgi mthod!");
            // 父进程数据
            auto &method = http_request.method;// 请求方法
            auto &query_string = http_request.query_string; // 请求报头
            auto &body_text = http_request.request_body; // 请求正文
            auto &bin = http_request.path;// 请求路径
            int content_length = http_request.content_length;// 请求正文长度
            auto &response_body = http_response.response_body;// 响应正文
        
            std::string query_string_env;
            std::string method_env;
            std::string content_length_env;

            int input[2];
            int output[2];

            // 创建双向管道
            if(pipe(input) < 0){
                // 创建失败
                LOG(ERROR,"pipe input error");
                code = SERVER_ERROR;
                return code;
            } 
            if(pipe(output) < 0){
                LOG(ERROR,"pipe output error");
                code = SERVER_ERROR;
                return code;
            }

            // 创建子进程
            pid_t pid = fork();
            if(pid == 0){// 子进程
                close(input[0]);
                close(output[1]);

                // 站在子进程角度
                // input[1]: 写出
                // output[0]: 读入

                method_env = "METHOD=";
                method_env += method;

                putenv((char*)method_env.c_str());// 增加环境变量
                // 环境变量不会被env给替换掉

                if(method == "GET"){
                    query_string_env = "QUERY_STRING=";
                    query_string_env += query_string;
                    putenv((char*)query_string_env.c_str());
                    LOG(INFO,"Get Method,Add Query_String Env");
                }
                else if(method == "POST"){
                    content_length_env = "CONTENT_LENGTH=";
                    content_length_env += std::to_string(content_length);
                    putenv((char*)content_length_env.c_str());
                    LOG(INFO,"Post Method,Add Content-Length Env");                   
                }
                else{

                }

                std::cout << "bin: " << bin << std::endl;
                
                // 重定向
                dup2(output[0],0);// 重定向到标准输入
                dup2(input[1],1);// 重定向到标准输出

                // 替换成功之后，目标子进程如何得知，对应读写文件描述符是多少呢？不需要，只要读0，写1即可
                execl(bin.c_str(),bin.c_str(),nullptr);// 进程替换
                std::cout<<"hello"<<std::endl;
                exit(1);
            }
            else if(pid < 0){// 创建子进程失败
                LOG(ERROR,"fork error!");
                return 404;
            }
            else{// 父进程
                close(input[1]);
                close(output[0]);

                // 站在父进程角度
                // input[0]: 读入
                // output[1]: 写出

                if(method == "POST"){
                    // 将请求正文读入管道
                    const char *start = body_text.c_str();
                    int total = 0;
                    int size = 0;
                    while(total < content_length && (size = write(output[1],start+total,body_text.size()-total)) > 0){
                        total += size;
                    }
                }

                // 获取响应主体
                char ch = 0;
                while(read(input[0],&ch,1) > 0){
                    response_body.push_back(ch);
                }

                int status = 0;
                pid_t ret = waitpid(pid,&status,0);// 等待子进程
                if(ret == pid){
                    if(WIFEXITED(status)){// 非0 表明进程正常结束
                        if(WEXITSTATUS(status) == 0){// 0表示进程正常终止
                            code = OK;
                        }
                        else{
                            code = SERVER_ERROR;
                        }
                    }
                    else{
                        code = SERVER_ERROR;
                    }
                }

                close(input[0]);
                close(output[1]);
            }
            return OK;
        }

        // 构建错误码的响应报头 
        void HandlerError(std::string page)
        {
            std::cout<<"debug: "<<page<<std::endl;
            http_request.cgi = false;
            
            http_response.fd = open(page.c_str(),O_RDONLY);// 给用户返回对应的错误页面
            if(http_response.fd > 0)
            {
                struct stat st;
                stat(page.c_str(),&st);
                http_request.size = st.st_size;// 更新打开页面的信息

                std::string line = "Content-Type: text/html";// 所以错误页面都是文本文件，文件类型都为text/html
                line += LINE_END;
                http_response.response_header.push_back(line);
                
                line = "Content-Length: ";// 错误页面文本文件的大小
                line += std::to_string(st.st_size);
                line += LINE_END;
                http_response.response_header.push_back(line);
            }
        }

        // 非cgi处理
        int ProcessNonCgi()
        {
            http_response.fd = open(http_request.path.c_str(),O_RDONLY);// 以只读形式打开文件
            if(http_response.fd >= 0){// 打开成功
                LOG(INFO,http_request.path + " open success!");
                return OK;// 打开失败
            }           
            return 404;
        }

        // 构建OK响应报头
        void BuildOkResponse()
        {
            std::string line = "Content-Type: ";
            line += Suffix2Desc(http_request.suffix);// 添加文件类型            
            line += LINE_END;// 添加/r/n
            http_response.response_header.push_back(line);// 添加到响应报头

            line = "Content-Length: ";
            if(http_request.cgi){
                line += std::to_string(http_response.response_body.size());// cgi的结果
            }
            else{
                line += std::to_string(http_request.size);// 打开文件的大小
            }
            line += LINE_END;
            http_response.response_header.push_back(line);
        }

        // 构建响应报文
        void BuildHttpResponseHelper()
        {   
            auto &code = http_response.status_code;
            // 构建状态行
            auto& status_line = http_response.status_line;
            status_line += HTTP_VERSION;
            status_line += " ";
            status_line += std::to_string(code);
            status_line += " ";
            status_line += Code2Desc(code);// 将状态码转换为字符串
            status_line += LINE_END;

            // 构建响应报文，可能包括响应报头
            std::string path = WEB_ROOT;
            path += "/";
            switch(code){
                case OK:
                    BuildOkResponse();  
                    break;
                case NOT_FOUND:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
                case BAD_REQUEST:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
                case SERVER_ERROR:
                    path += PAGE_404;
                    HandlerError(path);
                    break;
                default:
                    break;
            }
        }
    public:
        EndPoint(int _sock):sock(_sock),stop(false)
        {}

        bool IsStop()
        {
            return stop;
        }

        void RecvHttpRequest()
        {
            // 读取请求行和报头
            if((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader())){
                ParseHttpRequestLine();// 解析请求行
                ParseHttpRequestHeader();// 解析请求报头
                RecvHttpRequestBody();// 读取请求正文
            }
        }

        // 构建响应
        void BuildHttpResponse()
        {
            auto &code = http_response.status_code;// 状态码
            std::string _path;// 路径
            struct stat st;
            int size = 0;// 记录文件大小
            std::size_t found = 0;
            if(http_request.method != "GET" && http_request.method != "POST"){// 方法不存在设置错误码为400
                // 非法请求
                LOG(WARNING,"method is not right");
                code = BAD_REQUEST;
                goto END;
            }
            if(http_request.method == "GET"){
                // 将获取URI中的路径和参数
                size_t pos = http_request.uri.find('?');
                if(pos != std::string::npos){
                    Util::CutString(http_request.uri,http_request.path,http_request.query_string,"?");
                    http_request.cgi = true;// 需要cgi处理
                }
                else{
                    http_request.path = http_request.uri;
                }
            }
            else if(http_request.method == "POST"){
                // POST
                http_request.cgi = true;// 需要cgi处理
                http_request.path = http_request.uri;
            }

            _path = http_request.path;
            // 构建资源路径
            http_request.path = WEB_ROOT;// 在路径前添加wwwroot目录
            http_request.path += _path;
            if(http_request.path[http_request.path.size()-1] == '/'){
                http_request.path += HOME_PAGE;// 添加当前目录下的html网页
            }

            if(stat(http_request.path.c_str(),&st)==0){
                //资源是目录
                if(S_ISDIR(st.st_mode)){
                    // 请求的资源是一个目录需要做相关处理
                    // 注意，前文中我们把最后的'/'删去了，这里要添加上来
                    http_request.path += "/";
                    http_request.path += HOME_PAGE;
                    stat(http_request.path.c_str(),&st);// 更新st
                }
                // 资源是可执行程序
                if((st.st_mode&S_IXUSR) || (st.st_mode&S_IXGRP) || (st.st_mode&S_IXOTH)){
                    // cgi处理
                    http_request.cgi = true;
                }
                http_request.size = st.st_size;// 记录打开文件的大小
            }
            else{
                //资源不存在
                LOG(WARNING,http_request.path + "Not Found");
                code = NOT_FOUND;// 标记错误码
                goto END;
            }

            // 找后缀
            found = http_request.path.rfind(".");
            if(found == std::string::npos){
                http_request.suffix = ".html";
            }
            else{
                http_request.suffix = http_request.path.substr(found);
            }

            if(http_request.cgi){
                code = ProcessCgi();// 执行目标程序，拿到结果：http_response.response_body;
            }
            else{
                code = ProcessNonCgi();// 简单的网页返回，返回静态网页(打开即可)
            }
END:

            BuildHttpResponseHelper();// 构建响应报文
        }

        // 发送响应
        void SendHttpResponse()
        {
            send(sock,http_response.status_line.c_str(),http_response.status_line.size(),0);// 发送响应行
            for(auto iter : http_response.response_header){// 发送响应报头
                send(sock,iter.c_str(),iter.size(),0);
            }
            send(sock,http_response.blank.c_str(),http_response.blank.size(),0);// 响应空行

            if(http_request.cgi){
                // 如果是cgi处理，直接发送响应正文给浏览器
                auto &response_body = http_response.response_body;
                size_t size = 0;
                size_t total = 0;
                const char* start = response_body.c_str();
                while(total < response_body.size() && (size = send(sock,start+total,response_body.size() - total,0))>0)
                {
                    total += size;
                }
            }
            else{
                // 非cgi处理，将打开文件的内容发送给浏览器。
                sendfile(sock,http_response.fd,nullptr,http_request.size);
                close(http_response.fd);
            }
        }

        ~EndPoint()
        {
            close(sock);
        }
};

//#define DEBUG 1

class CallBack{
    public:
        CallBack ()
        {}
        void operator()(int sock)
        {
            HandlerRequest(sock);
        }
        void HandlerRequest(int sock)
        {
            LOG(INFO,"Hander Request Begin");

#ifdef DEBUG// 测试

            char buffer[4096];
            recv(sock,buffer,sizeof(buffer),0);
            std::cout<<"------------begin--------------"<<std::endl; 
            std::cout<<buffer<<std::endl;
            std::cout<<"------------end--------------"<<std::endl; 
#else 
            EndPoint *ep = new EndPoint(sock);
            ep->RecvHttpRequest();// 读取HTTP请求
            if(!ep->IsStop()){
                LOG(INFO,"Recv No Error,Begin Build And Send");
                ep->BuildHttpResponse();
                ep->SendHttpResponse();
            }
            else{
                LOG(WARNING,"Recv Error,Stop Build And Send");
            }

            delete ep;
#endif
            LOG(INFO,"Hander Request End");
        }

        ~CallBack()
        {}
};